﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections;
using BEG.Model;
using BEG.Model.Classifiers;
using BEG.Model.Projections;
using BEG.Core;
using BEG.Core.Interface;
using GalaSoft.MvvmLight.Command;
using System.Windows.Input;
using System.Xml;
using System.IO;
using BEG.Model.Attribute;

namespace WebReportGeneratorVer2
{
    /// <summary>
    /// Класс для генерации html-таблицы с информацией о классификаторах и проекциях
    /// </summary>
    public class HtmlReportGenerator
    {
        /// <summary>
        /// Номер строки, которая сейчас генерируется
        /// </summary>
        private int currentRow = 1;

        /// <summary>
        /// Ссылки на файлы с атрибутами классификаторов
        /// </summary>
        Dictionary<Classifier, string> classifierRefs = new Dictionary<Classifier, string>();

        /// <summary>
        /// Ширина каждого классификатора в таблице, включая атрибуты
        /// </summary>
        private int[] numberOfSecondAttributes;

        public HtmlReportGenerator()
        {

        }

        /// <summary>
        /// Сохраняет все классификаторы в отдельную директорию.
        /// Не создает отдельных файлов для фиктивных классификаторов
        /// </summary>
        /// <param name="classifiers">Список всех классификаторов</param>
        /// <param name="fileName">Имя основного файла для сохранения</param>
        /// <param name="projection">Проекция ,соединябщая выбранные классификаторы</param>
        public void writeClassifiersOneByOne(List<Classifier> classifiers, String fileName, Projection projection)
        {
            String pathToDir = fileName + " classifiers\\";
            if (Directory.Exists(pathToDir))
            {
                Directory.Delete(pathToDir, true);

            }

            Directory.CreateDirectory(pathToDir);
            for (int i = 0; i < classifiers.Count; i++)
            {
                List<Classifier> classifierToSave = new List<Classifier>();
                classifierToSave.Add(classifiers[i]);
                String path = pathToDir + classifierToSave[0].Name + ".html";
                
                // Determine whether the directory exists.
                while (i < classifiers.Count - 1 && classifiers[i+1].Name.Equals("Фиктивный классификатор"))
                {
                    i++;
                    classifierToSave.Add(classifiers[i]);
                }

                    classifierRefs.Add(classifierToSave[0], path);
                    // Delete the file if it exists.
                    if (File.Exists(path))
                    {
                        File.Delete(path);
                    }

                    //Create the file.
                    using (FileStream fs = File.Create(path))
                    {
                        StreamWriter writer = new StreamWriter(fs, Encoding.UTF8);
                        writeClassifiers(classifierToSave, writer, projection , true);
                        writer.Close();
                    }
            }
        }

        /// <summary>
        /// Записывает классификатор в поток
        /// </summary>
        /// <param name="с">Классификатор для записи</param>
        /// <param name="writer">Поток для записи</param>
        /// <param name="withArrtibutes">Записывать с атрибутами или без</param>
        public void writeClassifier(Classifier с, StreamWriter writer, bool withArrtibutes)
        {
            writer.WriteLine("<html>");
            writer.WriteLine("<body>");
            writer.WriteLine("<table border=3  bordercolor=black cellpadding=10 cellspacing=\"0\" border-collapse: collapse>");

            //заголовок

            writer.WriteLine("<tr>");
            writeHeader(с, writer, withArrtibutes);
            writer.WriteLine("</tr>");

            //надо определить направление проекции и расположение классификаторов
            foreach (ClassifierItem item in с.Items[0].Items)
            {
                //значение и атрибуты айтема первого классификатора пишем с rowspan равным количеству связанных айтемов (это будет количество строк)
                writer.WriteLine("<tr>");
                writeCell(item, writer, withArrtibutes, 0, 0);
            }


            writer.WriteLine("</table>");
            writer.WriteLine("</body>");
            writer.WriteLine("</html>");
        }

        /// <summary>
        /// Записывает набор связанных классификаторов в поток
        /// </summary>
        /// <param name="classifiers">Список классификаторов в порядке записи</param>
        /// <param name="writer">Поток для записи html</param>
        /// <param name="selectedProjection">Проекция, соединябщая классификаторы</param>
        /// <param name="withArrtibutes">С атрибутами или без</param>
        public void writeClassifiers(List<Classifier> classifiers, StreamWriter writer, Projection selectedProjection, bool withArrtibutes)
        {
            writer.WriteLine("<html>");
            writer.WriteLine("<body>");
            writer.WriteLine("<table border=3  bordercolor=black cellpadding=10 cellspacing=\"0\" border-collapse: collapse>");

            //заголовок

            writer.WriteLine("<tr>");
            writeHeader(classifiers[0], writer, withArrtibutes);

            //сколько у нас столбцов на каждый классификатор
            numberOfSecondAttributes = new int[classifiers.Count];
            for (int i = 1; i < classifiers.Count; i++)
            {
                numberOfSecondAttributes[i] = writeHeader(classifiers[i], writer, withArrtibutes);
            }

            writer.WriteLine("</tr>");

            foreach (ClassifierItem item in classifiers[0].Items[0].Items)
            {
                writer.WriteLine("<tr>");
                currentRow = 1;
                int maxRow = getConnectedItemsCount(selectedProjection, item, classifiers, 1);
                writeItem(classifiers, 0, writer, selectedProjection, withArrtibutes, item, maxRow);
            }

            writer.WriteLine("</table>");
            writer.WriteLine("</body>");
            writer.WriteLine("</html>");
        }

        /// <summary>
        /// Записывает в поток один элемент классификатора и связанные с ним элементы других классификаторов
        /// </summary>
        /// <param name="classifiers">Список классификаторов</param>
        /// <param name="curentClassifierPosition">Позиция классификатора, которому принадлежит записываемыый элемент</param>
        /// <param name="writer">Поток для записи</param>
        /// <param name="selectedProjection">Проекция, соединябщая элементы</param>
        /// <param name="withArrtibutes">Записывать с атрибутами или без</param>
        /// <param name="item">Элемент для записи</param>
        /// <param name="maxRow">Номер последней строки для подтаблицы, порождаемой этим элементом</param>
        private void writeItem(List<Classifier> classifiers, int curentClassifierPosition, StreamWriter writer, Projection selectedProjection, bool withArrtibutes, ClassifierItem item, int maxRow)
        {
            int rowSpan = getConnectedItemsCount(selectedProjection, item, classifiers, curentClassifierPosition + 1);

            //значение и атрибуты айтема первого классификатора пишем с rowspan равным количеству связанных айтемов (это будет количество строк)

            writeCell(item, writer, withArrtibutes, 0, rowSpan);

            //узнаем, есть ли связанные элементы и если нет, то записываем прочерки на оставшиеся позиции
            List<ClassifierItem> connected = getConnectedItems(selectedProjection, item, classifiers, curentClassifierPosition + 1);
            if (connected.Count == 0 & curentClassifierPosition != classifiers.Count - 1)
            {
                int numberOfTds = classifiers.Count - 1 - curentClassifierPosition;

                for (int k = curentClassifierPosition+1; k < numberOfSecondAttributes.Length; k++)
                {
                    numberOfTds += numberOfSecondAttributes[k];
                }

                    for (int i = 0; i < numberOfTds; i++)
                    {
                        writeTd(writer, 1);
                        writer.WriteLine("-");
                        writer.WriteLine("</td>");
                    }
                writer.WriteLine("</tr>");
                if (currentRow != maxRow)
                {
                    writer.WriteLine("<tr>");
                    currentRow++;
                }
            }

            //записываем связанные элементы на слелующих уровнях
            if (curentClassifierPosition < classifiers.Count - 1)
            {
                foreach (ClassifierItem connectedItem in connected)
                {

                    writeItem(classifiers, curentClassifierPosition + 1, writer, selectedProjection, withArrtibutes, connectedItem, maxRow);
                }
            }
            else
            {
                writer.WriteLine("</tr>");
                //если последняя строка, от не начинаем новую
                if (currentRow != maxRow)
                {
                    writer.WriteLine("<tr>");
                    currentRow++;
                }
            }
        }

        /// <summary>
        /// Количество строк, порождаемых записываемым элементов
        /// </summary>
        /// <param name="selectedProjection">Проекция</param>
        /// <param name="item">Записываемый элемент</param>
        /// <param name="classifiers">Список классификаторов</param>
        /// <param name="classifierPosition">Текущая позиция классфикатора в списке</param>
        /// <returns>Количество строк, порождаемых записываемым элементов</returns>
        private int getConnectedItemsCount(Projection selectedProjection, ClassifierItem item, List<Classifier> classifiers, int classifierPosition)
        {
            List<ClassifierItem> connectedItems = getConnectedItems(selectedProjection, item, classifiers, classifierPosition);
            int rowSpan = 0;
            foreach (ClassifierItem connectedItem in connectedItems)
            {
                if (classifierPosition == classifiers.Count - 1)
                {
                    //это последний классификатор
                    rowSpan++;
                }
                else
                {
                    rowSpan += getConnectedItemsCount(selectedProjection, connectedItem, classifiers, classifierPosition + 1);
                }
            }
            if (rowSpan == 0 & classifierPosition != classifiers.Count)
            {
                return 1;
            }

            return rowSpan;
        }

        /// <summary>
        /// Возвращает Список связанных элементов следующего классификатора в списке
        /// </summary>
        /// <param name="selectedProjection">Проекция</param>
        /// <param name="item">Записываемый элемент</param>
        /// <param name="classifiers">Список классификаторов</param>
        /// <param name="classifierPosition">Текущая позиция классфикатора в списке</param>
        /// <returns>Список связанных элементов</returns>
        private List<ClassifierItem> getConnectedItems(Projection selectedProjection, ClassifierItem item, List<Classifier> classifiers, int classifierPosition)
        {

            List<ClassifierItem> connectedItems = new List<ClassifierItem>();
            if (classifierPosition <= classifiers.Count - 1)
            {
                foreach (Link l in selectedProjection.GetLinksConnectedTo(item))
                {
                    ClassifierItem connectedItem = ClassifierUtils.getConnected(item, l);
                    if (connectedItem != null)
                    {
                        //если эта проекция выбрана и выбран классификатор, в кторый она ведет
                        if (selectedProjection.Equals(l.Projection) && connectedItem.Classifier.Equals(classifiers[classifierPosition]))
                        {
                            connectedItems.Add(connectedItem);
                        }
                    }

                }
            }
            return connectedItems;
        }

        /// <summary>
        /// Записывает заголовок классификатора
        /// </summary>
        /// <param name="c">Классификатор</param>
        /// <param name="writer">Поток для записи</param>
        /// <param name="withArrtibutes">С атрибутами или без</param>
        /// <returns></returns>
        private int writeHeader(Classifier c, StreamWriter writer, bool withArrtibutes)
        {
            writer.WriteLine("<td style = \"background-color:#7AC5CD\" " + ">");
            if (withArrtibutes)
            {
                writer.WriteLine(centered(bold(c.Items[0].Name)));
            }
            else
            {
                // вставить условие,чтобы без атрибутов классификатор не подсвечивался гиперссылкой!

                string refToFile = "";

                if (classifierRefs.TryGetValue(c, out refToFile))
                {
                    writeRef(writer, centered(bold(c.Items[0].Name)), "file:///" + refToFile);
                }
                else
                {
                    writer.WriteLine(centered(bold(c.Items[0].Name)));
                }

            }
            writer.WriteLine("</td>");
            //сколько столбцов будем отображать (нужно для генерации столбцов)
            int numberOfSecondAttributes = 0;

            if (withArrtibutes)
            {
                foreach (ClassifierAttribute attr in c.Items[0].Attributes)
                {
                    if (isColumnToShow(attr.HeaderObject))
                    {
                        numberOfSecondAttributes++;
                        writer.WriteLine("<td style = \"background-color:#7AC5CD\" " + ">");
                        writer.WriteLine(centered(bold(attr.HeaderObject.Name)));
                        writer.WriteLine("</td>");

                    }
                }
            }
            return numberOfSecondAttributes;
        }


        /// <summary>
        /// Записывает ячейку таблицы, соответствующую заданному элементу
        /// </summary>
        /// <param name="item">Элемент</param>
        /// <param name="writer">Поток для записи</param>
        /// <param name="withAttributes">С атрибутами/без</param>
        /// <param name="numberOfAdjacentAttributes">количество атрибутов элемента</param>
        /// <param name="rowspan">количество строк в таблице(html rowspan)</param>
        private void writeCell(ClassifierItem item, StreamWriter writer, bool withAttributes, int numberOfAdjacentAttributes, int rowspan)
        {

            if (item == null)
            {
                //записываем лишь пустые ячейки
                writeTd(writer, rowspan);
                writer.WriteLine("-");
                writer.WriteLine("</td>");
                if (withAttributes)
                {
                    for (int i = 0; i < numberOfAdjacentAttributes; i++)
                    {
                        writeTd(writer, rowspan);
                        writer.WriteLine(centered("-"));
                        writer.WriteLine("</td>");
                    }
                }
            }
            else
            {
                //находим ссылку на веб-сайт
                CommentHeader wwwAttributeHeader = null;

                foreach (ClassifierAttribute attr in item.Attributes)
                {
                    AttributeHeader header = attr.HeaderObject;
                    if ((header.Name.Equals("www") && header is CommentHeader))
                    {
                        wwwAttributeHeader = (CommentHeader)header;
                    }
                }

                //записываем значение элемента со ссылкой на сайт
                writeTd(writer, rowspan);
                if (wwwAttributeHeader != null)
                {
                    CommentAttribute attribute = (CommentAttribute)item.GetAttribute(wwwAttributeHeader);
                    if (attribute != null && attribute.Comment != null)
                    {
                        writeRef(writer, item.Name, attribute.Comment);
                    }
                    else
                    {
                        writer.WriteLine(item.Name);
                    }
                }

                else
                {
                    writer.WriteLine(item.Name);
                }
                writer.WriteLine("</td>");
                //записываем остальные атрибуты
                if (withAttributes)
                {
                    foreach (ClassifierAttribute attr in item.Attributes)
                    {
                        AttributeHeader header = attr.HeaderObject;
                        if (isColumnToShow(header))
                        {
                            CommentAttribute ca = (CommentAttribute)item.GetAttribute((CommentHeader)header);
                            writeTd(writer, rowspan);
                            writeComment(writer, ca);

                            writer.WriteLine("</td>");
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Возвращает центрированный текст
        /// </summary>
        /// <param name="s">Исходный текст</param>
        /// <returns>Центрированный текст</returns>
        private static string centered(String s)
        {
            return "<p align=\"center\">" + s + "</p>";
        }

        /// <summary>
        /// Следует ли выводить в таблице колонку, соответствующую атрибуту
        /// </summary>
        /// <param name="header">Заголовок атрибута</param>
        /// <returns>true, если выводим значения этого атрибута</returns>
        private static bool isColumnToShow(AttributeHeader header)
        {
            return header is CommentHeader && !header.Name.Equals("www") && !header.Name.Equals("Комментарий");
        }

        /// <summary>
        /// Записывает текст с гиперссылкой
        /// </summary>
        /// <param name="writer">Поток для записи</param>
        /// <param name="text">Текст</param>
        /// <param name="link">Ссылка</param>
        private static void writeRef(StreamWriter writer, String text, String link)
        {
            if (text != null)
            {
                writer.WriteLine("<a href=\"" + link + "\">" + text + "</a>");
            }
            else
            {
                writer.WriteLine("-");
            }
        }

        /// <summary>
        /// Записывает значение атрибута. Прочерк если значение не установлено
        /// </summary>
        /// <param name="writer">Поток для записи</param>
        /// <param name="ca">атрибут</param>
        private static void writeComment(StreamWriter writer, CommentAttribute ca)
        {
            if (ca.Comment != null)
            {
                writer.WriteLine(ca.Comment);
            }
            else
            {
                writer.WriteLine("-");
            }
        }

        /// <summary>
        /// Записывает тег td с заданным rowspan
        /// </summary>
        /// <param name="writer">Поток для записи</param>
        /// <param name="rowspan"></param>
        private static void writeTd(StreamWriter writer, int rowspan)
        {
            if (rowspan < 2)
            {
                writer.WriteLine("<td style = \"background-color:#F0FFFF\" " + ">");
            }
            else
            {
                writer.WriteLine("<td style = \"background-color:#F0FFFF\" rowspan=" + rowspan + ">");
            }
        }

        /// <summary>
        /// Текст жирным начертанием
        /// </summary>
        /// <param name="s">Текст</param>
        /// <returns>Данную строку с тегами жирного шрифта</returns>
        private static string bold(String s)
        {
            return "<b>" + s + "</b>";
        }
    }
}
